#ifndef _LUABIND_TIMER_H
#define _LUABIND_TIMER_H

#include "util/z9_timer_define.h"
#include "util/z9_timer.h"
#include "lua/z9_lua.h"
#include "util/z9_log.h"
#include "util/z9_time.h"

#define TIMERMGR_METATABLE "lua_timermgr"

#define TIMER_METATABLE "lua_timer"

void z9_timermgr_init(z9_timermgr *);

void z9_timermgr_finalize(z9_timermgr *);

typedef struct {
	z9_timer *timer;
	z9_luaRef cb;
} lua_timer;

static void timer_ud_cleaner(void *ud) {
	lua_timer *luatimer = (lua_timer*)ud;
	luatimer->timer = NULL;
	z9_luaRef_release(&luatimer->cb);
}

static int32_t lua_timeout_cb(uint64_t tick,void*ud) {
	lua_timer *luatimer = (lua_timer*)ud;
	const char *error; 
	lua_Integer ret;
	if(NULL != (error = z9_Lua_PCallRef(luatimer->cb,":i",&ret))) {
		Z9_SYSLOG(LOG_ERROR,"error on lua_timeout_cb %s",error);
		return -1;
	}
	return (int32_t)ret;	
}

#define lua_checktimermgr(L,I)	\
	(z9_timermgr*)luaL_checkudata(L,I,TIMERMGR_METATABLE)

#define lua_checktimer(L,I)	\
	(lua_timer*)luaL_checkudata(L,I,TIMER_METATABLE)

static int32_t lua_timer_gc(lua_State *L) {
	lua_timer *luatimer = lua_checktimer(L,1);
	if(luatimer->timer) {
		z9_timer_unregister(luatimer->timer);
		z9_luaRef_release(&luatimer->cb);
	}
	return 0;
}

static int32_t lua_timermgr_gc(lua_State *L) {
	z9_timermgr *timermgr = lua_checktimermgr(L,1);
	z9_timermgr_finalize(timermgr);
	return 0;
}

static int32_t lua_new_timermgr(lua_State *L) {
	if(!LUA_NEWUSERDATA(L,z9_timermgr))
		return 0;
	luaL_getmetatable(L, TIMERMGR_METATABLE);
	lua_setmetatable(L, -2);
	return 1;
}

static int32_t lua_timermgr_register(lua_State *L) {
	z9_luaRef    cb;
	uint32_t      ms;
	uint64_t      tick;
	lua_timer    *luatimer;
	z9_timermgr *timermgr = lua_checktimermgr(L,1);
	ms  = (uint32_t)luaL_optinteger(L,2,1);
	if(!lua_isfunction(L,3)) 
		return luaL_error(L,"argument 3 of event_loop_addtimer must be lua function"); 
	cb = z9_toluaRef(L,3);

	luatimer = LUA_NEWUSERDATA(L,lua_timer);
	if(!luatimer) {
		Z9_SYSLOG(LOG_ERROR,"LUA_NEWUSERDATA() failed");
		return 0;
	}
	luatimer->cb = cb;
	tick = z9_accurate_tick64();
	luatimer->timer = z9_timer_register(timermgr,ms,lua_timeout_cb,luatimer,tick);

	if(luatimer->timer)
		z9_timer_set_ud_cleaner(luatimer->timer,timer_ud_cleaner);
	else {
		z9_luaRef_release(&luatimer->cb);
		Z9_SYSLOG(LOG_ERROR,"z9_loop_addtimer() failed");
		return 0;
	}
	luaL_getmetatable(L, TIMER_METATABLE);
	lua_setmetatable(L, -2);
	return 1;
}

static int32_t lua_timermgr_tick(lua_State *L) {
	z9_timermgr *timermgr = lua_checktimermgr(L,1);
	z9_timer_tick(timermgr,z9_systick64());
	return 0;
}

static int32_t lua_unregister_timer(lua_State *L) {
	lua_timer *luatimer = lua_checktimer(L,1);
	if(luatimer->timer) {
		z9_timer_unregister(luatimer->timer);
		luatimer->timer = NULL;
		z9_luaRef_release(&luatimer->cb);
	}
	return 0;
}


static void register_timer(lua_State *L) {
	luaL_Reg timermgr_mt[] = {
		{"__gc", lua_timermgr_gc},
		{NULL, NULL}
	};

	luaL_Reg timermgr_methods[] = {
		{"tick",    lua_timermgr_tick},
		{"regTimer",lua_timermgr_register},
		{NULL,     NULL}
	};

	luaL_Reg timer_mt[] = {
		{"__gc", lua_timer_gc},
		{NULL, NULL}
	};

	luaL_Reg timer_methods[] = {
		{"unRegister",    lua_unregister_timer},
		{NULL,     NULL}
	};	

	luaL_newmetatable(L, TIMERMGR_METATABLE);
	luaL_setfuncs(L, timermgr_mt, 0);

	luaL_newlib(L, timermgr_methods);
	lua_setfield(L, -2, "__index");
	lua_pop(L, 1);

	luaL_newmetatable(L, TIMER_METATABLE);
	luaL_setfuncs(L, timer_mt, 0);

	luaL_newlib(L, timer_methods);
	lua_setfield(L, -2, "__index");
	lua_pop(L, 1);	

	lua_newtable(L);
	SET_FUNCTION(L,"new",lua_new_timermgr);
}

#endif
